﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Text;
using Defero.CrossCutting;
using Defero.CrossCutting.Entities;

namespace Defero.Infrastucture
{
    public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : EntityObject
    {
        /// <summary>
        /// Constructor default.
        /// </summary>
        /// <param name="context">Contexto de datos.</param>
        public GenericRepository(ObjectContext context)
        {
            Context = context;              
            EntitySetName =
                Context.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.CSpace)
                .First()
                .BaseEntitySets
                .FirstOrDefault(item => item.ElementType.Name == typeof(TEntity).Name)
                .Name;
            
            Data = context.CreateObjectSet<TEntity>(EntitySetName);
            ((ObjectSet<TEntity>)Data).MergeOption = MergeOption.NoTracking;
        }
        /// <summary>
        /// Enlaza una entidad al contexto actual
        /// </summary>
        /// <param name="entity">Entidad que sera enlazada</param>
        private TEntity Attach(TEntity entity) 
        {
            entity.EntityKey = Context.CreateEntityKey(EntitySetName, entity);
            Context.Attach(entity);

            return entity;
        }
        /// <summary>
        /// Modifica el estado interno de una entidad
        /// </summary>
        /// <param name="entity">Entidad que sera modificada</param>
        /// <param name="state">Estado en el que se establecera la entidad.</param>
        private TEntity SetState(TEntity entity, EntityState state) 
        {
            Attach(entity);
            Context.ObjectStateManager.ChangeObjectState(entity, state);

            return entity;
        }
        /// <summary>
        /// Nombre del repositorio de datos.
        /// </summary>
        private string EntitySetName { get; set; }
        /// <summary>
        /// Repositorio de datos.
        /// </summary>
        private ObjectSet<TEntity> Data { get; set; }
        /// <summary>
        /// Contexto de datos.
        /// </summary>
        public ObjectContext Context { get; set; }
        /// <summary>
        /// Devuelve todos los registros disponibles;
        /// </summary>
        public IEnumerable<TEntity> All()
        {
            return Data.AsEnumerable();
        }
        /// <summary>
        /// Deveuelve todos los registros que cumplan con una condición dada.
        /// </summary>
        /// <param name="exp">Clausula condicionante.</param>
        public IEnumerable<TEntity> All(Func<TEntity, bool> exp)
        {
            return Data.Where(exp);
        }
        /// <summary>
        /// Devuelve el primer registro del resultado de una condición.
        /// </summary>
        /// <param name="exp">Clausula condicionante.</param>
        public TEntity First(Func<TEntity, bool> exp)
        {
            return Data.First(exp);
        }
        /// <summary>
        /// Devuelve el ultimo registro del resultado de una condición.
        /// </summary>
        /// <param name="exp">Clausula condicionante.</param>
        public TEntity Last(Func<TEntity, bool> exp)
        {
            return Data.Last(exp);
        }
        /// <summary>
        /// Determina si existe por lo menos un registro que cumpla con una condición.
        /// </summary>
        /// <param name="exp">Clausula condicionante.</param>
        public bool Contains(Func<TEntity, bool> exp)
        {
            return Data.Any(exp);
        }
        /// <summary>
        /// Inserta un registro.
        /// </summary>
        /// <param name="entity">Objeto a insertar</param>
        public void Insert(TEntity entity)
        {
            SetState(entity, EntityState.Added);
        }
        /// <summary>
        /// Elimina un registro.
        /// </summary>
        /// <param name="entity">Objeto a eliminar.</param>
        public void Delete(TEntity entity)
        {
            SetState(entity, EntityState.Deleted);
        }
        /// <summary>
        /// Actualiza un registro.
        /// </summary>
        /// <param name="entity">Objeto a actualizar.</param>
        public void Save(TEntity entity)
        {
            SetState(entity, EntityState.Modified);
        }
        /// <summary>
        /// Ejecuta todos los cambios pendientes.
        /// </summary>
        public void Submit()
        {
            Submit(SaveOptions.AcceptAllChangesAfterSave);
        }
        /// <summary>
        /// Ejecuta todos los cambios pendientes.
        /// </summary>
        /// <param name="conflicMode">Modo de resolución de conflictos.</param>
        public void Submit(SaveOptions options)
        {
            Context.SaveChanges(options);
        }
    }
}
